package com.zhenhunfan.io.model.nio.demo02.demo01;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * <p>Title: NioServer</p>
 * <p>Description: 基于nio的reactor模型</p>
 *
 * @author zhenhunfan
 * @version 1.0.0
 * @date 2021/4/1
 */
public class NioServer2 {
    //多路复用器，主要作用是轮询TCP缓冲区中每个网络IO的数据是否准备好了
    private final Selector selector;
    //可以理解成web服务器
    private final ServerSocketChannel serverSocketChannel;

    public NioServer2(int port) throws IOException { //reactor 初始化
        selector = Selector.open();//打开一个Selector，找内核申请多路复用器
        serverSocketChannel = ServerSocketChannel.open();//建立一个Server端通道
        serverSocketChannel.socket().bind(new InetSocketAddress(port));//绑定服务端口
        serverSocketChannel.configureBlocking(false);//selector模式下，所有通道必须是非阻塞的

        //服务器通道告诉selector去关注accept事件（可以理解成客户端（比方说浏览器）发送连接请求了），一旦有accept事件
        //则生成一个SelectionKey sk去处理，怎么处理呢？就是给sk绑定事件处理器
        //最初给一个channel注册上去的事件都是accept
        SelectionKey sk = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        //attach callback object，Acceptor
        sk.attach(new Acceptor2(serverSocketChannel,selector));//绑定接收事件处理器
    }

    public void run(){
        try {
            while (!Thread.interrupted()){
                System.out.println("start");
                //阻塞获取就绪状态的事件，如果你对“读就绪”的通道感兴趣，select()方法会返回读事件已经就绪的那些通道
                selector.select();//就绪事件到达之前，阻塞
                Set selected = selector.selectedKeys();//拿到本次select获取的就绪事件
                Iterator it = selected.iterator();
                while (it.hasNext()){
                    //这里进行任务分发
                    dispatch((SelectionKey)(it.next()));
                }
                selected.clear();
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(selector != null){
                try {
                    selector.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    void dispatch(SelectionKey k){
        Runnable r = (Runnable)(k.attachment());
        //调用之前注册的callback对象
        if(r != null){
            //只是拿到句柄执行run方法，并没有起新线程
            //新起线程的写法为new Thread(r).start();
            r.run();
        }
    }

    public static void main(String[] args) throws IOException {
        NioServer2 server = new NioServer2(8080);
        server.run();
    }
}
